Flask restful源码分析

参考地址:https://note.youdao.com/ynoteshare1/index.html?id=4ef343068763a56a10a2ada59a019484&type=note

Flask restful 简单示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)


class HelloWorld(Resource):
def get(self):
return {'hello': 'world'}


api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
app.run(debug=True)

使用 flask_restful 的 Api 封装 app 对象,并且自定义请求类,使用 add_resource 将路由和类进行链接,并请求对应的方法。在 Flask 中,每个路由对应的是一个方法,flask_restful 对应的是一个类,里面做了怎样的处理呢?add_resource 源码如下:

1
2
3
4
5
def add_resource(self, resource, *urls, **kwargs):
if self.app is not None:
self._register_view(self.app, resource, *urls, **kwargs)
else:
self.resources.append((resource, urls, kwargs))

_register_view 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  def _register_view(self, app, resource, *urls, **kwargs):
# app 代表当前 app 对象,resource 是路由对应的类
# 获取 endpoint,如果没有指定则使用路由对应的类的名称
endpoint = kwargs.pop('endpoint', None) or resource.__name__.lower()
# self.endpoints 的类型是 set,所有 endpoint 不能重复
self.endpoints.add(endpoint)
# 获取要传递给 resource 类的 args 参数
resource_class_args = kwargs.pop('resource_class_args', ())
# 获取要传递给 resource 类的 kwargs 参数
resource_class_kwargs = kwargs.pop('resource_class_kwargs', {})

if endpoint in getattr(app, 'view_functions', {}):
previous_view_class = app.view_functions[endpoint].__dict__['view_class']
if previous_view_class != resource:
raise ValueError('This endpoint (%s) is already set to the class %s.' % (endpoint, previous_view_class.__name__))
# 获取 mediatypes
resource.mediatypes = self.mediatypes_method() # Hacky
resource.endpoint = endpoint
# 这里很重要
# 路由对应的类 resource 继承的是 Resource,Resource 继承自 MethodView 并且重写了 dispatch_request,MethodView 的元类是 MethodViewType 和 View。所以,resource.as_view 调用的是 View 中的 as_view 方法,as_view 方法的返回是一个函数,只不过函数中存在路由对应的类的属性。
# self.output 的参数是 resource.as_view 的返回值。
resource_func = self.output(resource.as_view(endpoint, *resource_class_args,
**resource_class_kwargs))

for decorator in self.decorators:
resource_func = decorator(resource_func)

for url in urls:
# If this Api has a blueprint
if self.blueprint:
# And this Api has been setup
if self.blueprint_setup:
# Set the rule to a string directly, as the blueprint is already
# set up.
self.blueprint_setup.add_url_rule(url, view_func=resource_func, **kwargs)
continue
else:
# Set the rule to a function that expects the blueprint prefix
# to construct the final url. Allows deferment of url finalization
# in the case that the associated Blueprint has not yet been
# registered to an application, so we can wait for the registration
# prefix
rule = partial(self._complete_url, url)
else:
# If we've got no Blueprint, just build a url with no prefix
rule = self._complete_url(url, '')
# 最后还是调用 flask 的 add_url_rule 方法将路由和函数进行关联映射
app.add_url_rule(rule, view_func=resource_func, **kwargs)

as_view 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@classmethod
def as_view(cls, name, *class_args, **class_kwargs):
# cls 是当前路由所对应的 resource 类
# name 是 endpoint
# class_args 是要传递给 resource 类的 args 参数
# class_kwargs 是要传递给 resource 类的 kwargs 参数
"""
将类转换为可与路由系统一起使用的实际视图函数。内部会动态生成一个函数,该函数将在每个请求上实例化View类,并在其上调用dispatch_request方法。
"""
def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs)

if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)
# view.view_class 就是当前 resource
view.view_class = cls
view.__name__ = name
# 将当前 resource 的 __doc__,__module__,methods 赋给函数的对应的属性
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.methods = cls.methods
view.provide_automatic_options = cls.provide_automatic_options
# 返回一个函数,且这个函数里面调用了当前 resource 的 dispatch_request 方法
return view

因为 HelloWorld 类是继承 Resource 类的,所以在 as_view 中调用的 dispatch_request 方法就会调用到 Resource 中被重写的 dispatch_request 方法

1
2
3
class HelloWorld(Resource):
def get(self):
return {'hello': 'world'}

Resourcedispatch_request 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Resource(MethodView):
representations = None
method_decorators = []

def dispatch_request(self, *args, **kwargs):
# 获取当前请求方法,并寻找类中对应的方法
meth = getattr(self, request.method.lower(), None)
if meth is None and request.method == 'HEAD':
meth = getattr(self, 'get', None)
assert meth is not None, 'Unimplemented method %r' % request.method
# 获取方法的装饰器
if isinstance(self.method_decorators, Mapping):
decorators = self.method_decorators.get(request.method.lower(), [])
else:
decorators = self.method_decorators
# 将所有装饰器装饰到方法上
for decorator in decorators:
meth = decorator(meth)
# 执行请求所对应的方法
resp = meth(*args, **kwargs)
# 如果方法对应返回就是一个 Response 对象,那么就直接返回
if isinstance(resp, ResponseBase): # There may be a better way to test
return resp
# 否则构建返回
representations = self.representations or OrderedDict()

#noinspection PyUnresolvedReferences
mediatype = request.accept_mimetypes.best_match(representations, default=None)
if mediatype in representations:
data, code, headers = unpack(resp)
resp = representations[mediatype](data, code, headers)
resp.headers['Content-Type'] = mediatype
return resp

return resp

具体的方法执行之后,就会回到外层的 self.output 函数,self.output 源码如下:

1
2
3
4
5
6
7
8
9
10
11
def output(self, resource):
# 这里的 resource 是 resource.as_view 返回的函数
@wraps(resource)
def wrapper(*args, **kwargs):
resp = resource(*args, **kwargs)
if isinstance(resp, ResponseBase): # There may be a better way to test
return resp
data, code, headers = unpack(resp)
return self.make_response(data, code, headers=headers)
# output 返回的也是一个函数,准确来说 output 是一个装饰器
return wrapper

启动流程:

  1. add_resource 调用 _register_view
  2. _register_view 调用 as_view 方法,as_view 返回 view 一个函数。资源类和 view 函数进行绑定。并且 view 中要调用 对应的 dispatch_request 方法
  3. 使用 output 装饰器对上述返回的函数进行装饰,返回一个被装饰的函数
  4. 调用 Flask add_url_rule 方法将上述返回的被装饰函数和路径进行绑定
  5. 执行 Flask 启动流程

调用流程

  1. Flask 请求进行,找到路由对应的函数
  2. 执行 output 函数,调用 view 函数
  3. view 函数中调用对应的 dispatch_request 函数
  4. 执行 Resource 中重写的 dispatch_request 函数,将请求转换为调用类中对应的方法
  5. 执行请求对应的目标方法
  6. 回到 output 方法,调用 make_response 构建返回
  7. 执行 Flask 返回流程